package simpleExample;

import gridworld.Environment;

import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;

import jade.domain.DFService;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.domain.FIPAException;

import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.lang.acl.UnreadableException;

import java.awt.Point;

import java.util.HashSet;


public class CarrierBehaviour extends Behaviour {
    //current destination
    private Point destination;
    
    //current position
    private Point currentPosition;
    
    //the list of known spotters
    private AID[] spotters;
    
    private String agentName;
    
    private byte state;
    
    public static final byte REQUEST_FOR_LOCATION = 2;
    
    //the current spotter index for requesting info
    private int currentSpotter;

    private static final long messageTimeout = 1000;
    
    //list of known trap locations
    private HashSet<Point> traps;
    
    private static final int TRAP_REQUEST_FR = 10;
    
    private int trap_step;
    
    private final MessageTemplate trapTemplate = 
        MessageTemplate.MatchPerformative(ACLMessage.INFORM);
    
    private final MessageTemplate bombTemplate = 
        MessageTemplate.MatchPerformative(ACLMessage.INFORM_IF);
    
    public CarrierBehaviour(Agent agent) {
        super(agent);
        destination = null;
        agentName = agent.getLocalName();
        state = SimpleAgent.NO_KNOWLEDGE;
        currentSpotter = 0;
        traps = new HashSet<Point>();
        trap_step = 0;
        DFAgentDescription template = new DFAgentDescription();
        ServiceDescription sd = new ServiceDescription();
        sd.setType("spotting-bombs");
        template.addServices(sd);
        try {
            //try to search for spotters
            DFAgentDescription[] result;
            do {
              result = DFService.search(myAgent, template);
              spotters = new AID[result.length];
              for (int i = 0; i < result.length; i++) {
                  spotters[i] = result[i].getName();
              }
            //if you know at least a spotter
            } while(result.length <= 0);
        } catch (FIPAException e) {
            e.printStackTrace();
        }
    }

    public void action() {
        if(traps.isEmpty() || (trap_step == TRAP_REQUEST_FR)) {
            //the spotter is changed automatically
            sendMessageToSpotter(ACLMessage.REQUEST, SimpleAgent.TRAP_REQUEST);
            ACLMessage msg = myAgent.blockingReceive(trapTemplate, 
                                                     messageTimeout);
            if(msg != null) {
                try {
                    traps.addAll((HashSet<Point>)msg.getContentObject());
                } catch (UnreadableException e) {
                    System.err.println("Unable to process trap message");
                }
            }
            //set the trap step to 0 after making request
            trap_step = 0;
        }
        //increase the counter for requesting traps
        trap_step++;
        currentPosition = Environment.getPosition(agentName);
        switch(state) {
        case SimpleAgent.NO_KNOWLEDGE: {
            //request a bomb from the spotters
            if(currentSpotter < spotters.length) {
                sendMessageToSpotter(ACLMessage.REQUEST, SimpleAgent.
                                     pointToString(currentPosition));
                state = REQUEST_FOR_LOCATION;
            } else {
                System.out.println("miau");
                /*we don't know any spotter that can provide info
                 * just block in this state */
                block(5 * messageTimeout);
                currentSpotter = 0;
            }
        }break;
        case REQUEST_FOR_LOCATION: {
            ACLMessage reply = myAgent.blockingReceive(bombTemplate, 
                                                       messageTimeout);
            if(reply != null) {
                String content = reply.getContent();
                if(!content.equals("")) {
                    destination = SimpleAgent.pointFromString(content);
                    state = SimpleAgent.KNOW_BOMB_LOCATION;
                }
            } else {
                //send a message to the next spotter
                currentSpotter = (1 + currentSpotter) % spotters.length;
                state = SimpleAgent.NO_KNOWLEDGE;
            }
        }break;
        case SimpleAgent.KNOW_BOMB_LOCATION: {
            //move to the location of the bomb
            if(destination.equals(currentPosition)) {
                //we can pick up the bomb
                if(Environment.takeBomb(agentName)) {
                    //inform the spotter that there is no bomb there
                    sendMessageToSpotter(ACLMessage.INFORM, (int)destination.getX() + 
                                        "," + (int)destination.getY());
                    //set the destination to the nearest trap
                    destination = SimpleAgent.getNearestTarget(currentPosition, 
                                                               traps);
                    state = SimpleAgent.HAS_BOMB;
                } else {
                    /* someone has taken the bomb already so request info for
                     * another bomb location
                     */
                    state = SimpleAgent.NO_KNOWLEDGE;
                }
            } else {
                moveToDestination();    
            }
        }break;
        case SimpleAgent.HAS_BOMB: {
            //if we know the location of some trap
            if(destination != null)  {
                currentPosition = Environment.getPosition(agentName);
                if(destination.equals(currentPosition)) {
                    //drop the bomb and request another bomb location
                    Environment.dropBomb(agentName);
                    state = SimpleAgent.NO_KNOWLEDGE;
                } else {
                    moveToDestination();  
                }
            } else {
                //else try to find another trap and increase the spotter idx.
                currentSpotter = (1 + currentSpotter) % spotters.length;
                destination = SimpleAgent.getNearestTarget(currentPosition, 
                                                           traps);
            }
        }break;
        }
    }

    private void moveToDestination() {
        //try to move to destination
        ((SimpleAgent)myAgent).moveAgent(destination);
    }
    
    public boolean done() {
        return false;
    }
    
    private void sendMessageToSpotter(int performative, String content) {
      ACLMessage m = new ACLMessage(performative);
      m.addReceiver(spotters[currentSpotter]);
      m.setContent(content);
      myAgent.send(m);
    }
}
